import subprocess
from subprocess import PIPE
import os
import shutil
import time
import datetime
import logging
import logging.config
import MySQLdb as sql

DATE = datetime.datetime.now()

#--- PATHS AND FILES ---
DATEFILE = '-{DATE.year}-{DATE.month}-{DATE.day}T{DATE.hour}-{DATE.minute}.tar.gz'.format(**locals())
ROOTPATH = '/tmp/backup'
COPYPATH = '/tmp/backup_complete'
FINALFILES = '/mnt/backup/files'
RECALL = '/tmp/recall.txt'
 # SQL
SQLPATH = os.path.join(ROOTPATH, 'mysql')
TEMPSQL = os.path.join(SQLPATH, 'mysql.sql')
OUTSQL = os.path.join(COPYPATH, 'mysql{}'.format(DATEFILE))
 # nginx
SRCNGINX = '/etc/nginx'
OUTNGINX = os.path.join(COPYPATH, 'nginx{}'.format(DATEFILE))
 # newznab
SRCNN = '/var/www/newznab'
OUTNN = os.path.join(COPYPATH, 'nnsite{}'.format(DATEFILE))
 # newznab nzbs
SRCNZB = '/var/www/newznab/nzbfiles'
DESTNZB = '/mnt/backup/rsync'
#--- END PATHS AND FILES ---

#--- MYSQL INFO ---
MYSQL_USER = 'root'
MYSQL_PASS = ''
MYSQL_DB = 'newznab'
#--- END MYSQL INFO ---

#--- MAKE DIRECTORIES ---
try:
    os.makedirs(COPYPATH)
except:
    print 'Directory exists.'
try:
    os.makedirs(SQLPATH)
except:
    print 'Directory exists.'
#--- END MAKE DIRECTORIES ---

#--- LOGGING FACILITY ---
logging.config.fileConfig('/home/robert/backup_log.txt')
logProcManager = logging.getLogger('proc')
logSQL = logging.getLogger('sql')
logTar = logging.getLogger('tar')
logRsync = logging.getLogger('rsync')
logMain = logging.getLogger('main')
#--- END LOGGING FACILITY ---


class ProcessManager(object):

    def __init__(self, recallFileName):
        self.procNames = []
        self.file = recallFileName
        self.createFile()

    def getPID(self, name):
        pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
        for pid in pids:
            try:
                with open(os.path.join('/proc', pid, 'cmdline'), 'r') as f:
                    process = f.read()
                if name in process:
                    return int(pid)
            except:
                continue
        return None

    def addProc(self, name):
        self.procNames.append(name)

    def checkProc(self, name, sigPause, sigConfirm):
        t_wait = 0
        confirm_safe = None
        if self.getPID(name):
            logProcManager.info('Process {} running.'.format(name))
            if not sigPause in self.readFile():
                self.writeFile(sigPause)
            while not confirm_safe and t_wait < 3600:
                logProcManager.info('Waiting...')
                if sigConfirm in self.readFile():
                    logProcManager.info('Process paused.')
                    return True
                else:
                    time.sleep(120)
                    t_wait += 120
            logProcManager.warning('Could not pause process.')
            confirm_safe = False
        else:
            logProcManager.warning('Process not found.')
            confirm_safe = True
        return confirm_safe


    def killSingleProc(self, name):
        import errno
        safe = None
        pid = self.getPID(name)
        if not pid:
            logProcManager.warning('Process not running.')
            return True
        try:
            logProcManager.info('Killing process {} with PID {}'.format(name, pid))
            os.kill(pid, 2)
        except OSError as e:
            if e.errno == errno.ESRCH:
                logProcManager.warning('Process is not running.')
            elif e.errno == errno.EPERM:
                logProcManager.warning('No permission to kill process.')
            safe = False
        else:
            logProcManager.info('Process killed successfully.')
            safe = True
        return safe

    def killAllProc(self):
        for procName in self.procNames:
            self.killSingleProc(procName, self.pidDict[procName])

    def createFile(self):
        if not os.path.exists(self.file):
            open(self.file, 'w').close()
            os.chmod(self.file, 0777)

    def readFile(self):
        try:
            with open(self.file, 'r') as f:
                return f.read()
        except OSError:
            return None

    def writeFile(self, data):
        try:
            with open(self.file, 'a') as f:
                if data:
                    f.write(data)
        except OSError:
            return None

    def delFile(self):
        if os.path.exists(self.file):
            try:
                os.remove(self.file)
            except OSError:
                return None


def attemptConnect():
    try:
        conn = sql.connect(user=MYSQL_USER, passwd=MYSQL_PASS, db=MYSQL_DB)
    except:
        logSQL.error('Could not connect to database {} with user {}.'.format(MYSQL_DB, MYSQL_USER))
        return False
    else:
        logSQL.info('Successfully connected to database {} with user {}.'.format(MYSQL_DB, MYSQL_USER))
        conn.close()
        return True


def doDbDump():
    DUMP_CMD = 'mysqldump --user={} --password={} {}'.format(MYSQL_USER, MYSQL_PASS, MYSQL_DB)
    logSQL.info('Beginning dump process.')
    dump = subprocess.Popen(DUMP_CMD, shell=True, stdout=PIPE, stderr=PIPE)
    logSQL.info('Dump completed; processing.')
    try:
        with open(TEMPSQL, 'w') as f:
            logSQL.info('Writing file.')
            f.writelines(dump.stdout)
    except Exception as exc:
        logSQL.error('Error: {}'.format(exc))
        return False
    else:
        logSQL.info('File written successfully.')
        return True


def tarFiles(source=None, destination=None, exclude=[]):
    logTar.info('Source {}'.format(source))
    logTar.info('Destination {}'.format(destination))
    logTar.info('Exclude: {}'.format(str(exclude)))
    cmd = ['tar']
    params = ['-czf', destination]
    if exclude:
        params.extend(['--exclude={}'.format(e) for e in exclude])
    params.append(source)
    cmd.extend(params)
    try:
        subprocess.check_call(cmd)
    except subprocess.CalledProcessError as e:
        logTar.error('Error backing up: {}'.format(e))
    except Exception as e:
        logTar.error('Some other error occurred: {}'.format(e))
    else:
        logTar.info('Backup process successful.')


def doRsync(source=None, destination=None):
    logRsync.info('Source {}'.format(source))
    logRsync.info('Destination {}'.format(destination))
    cmd = ['rsync', '-avr', '--progress', '--delete', source, destination]
    try:
        subprocess.check_call(cmd)
    except subprocess.CalledProcessError as e:
        logRsync.error('rsync failed: {}'.format(e))
    else:
        logRsync.info('Completed rsync.')


def killProcesses(recallFile):
    procMan = ProcessManager(recallFile)
    if not procMan.checkProc('safebackfill.py', 'stop_update\n', 'update_waiting'):
        return None
    if not procMan.killSingleProc('justpostprocessing.php'):
        if not procMan.checkProc('justpp.py', 'stop_post\n', 'post_waiting'):
            return None
    return procMan


def trimBackups():
    pass


def main():
    logMain.info('Pausing processes.')
    procMan = killProcesses(RECALL)
    if procMan:
        logMain.info('Check connections.')
        if attemptConnect():
            if doDbDump():
                tarFiles(TEMPSQL, OUTSQL)
        logMain.info('Backing up files.')
        logMain.info('Backing up newznab.')
        tarFiles(SRCNN, OUTNN, ['nzbfiles', 'sphinxdata'])
        logMain.info('Backing up nginx.')
        tarFiles(SRCNGINX, OUTNGINX)
        logMain.info('Backing up nzb files.')
        doRsync(SRCNZB, DESTNZB)
    else:
        logMain.error('There was a problem getting processes to pause.  Skipping backup.')
    logMain.info('Cleaning up...')
    try:
        os.remove(RECALL)
        shutil.rmtree(ROOTPATH)
    except Exception as e:
        logMain.error('Error cleaning up: {}'.format(e))
    logMain.info('Done.')
    logging.shutdown()




if __name__ == '__main__':
    main()
